/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package testGenerator.antlr.testBuilders;

import gui.GUIController;
import java.util.ArrayList;
import java.util.Stack;
import testGenerator.Controller;
import testGenerator.testMethodModel.AutoJUnitMethod;
import testGenerator.testMethodModel.EnumCondType;
import testGenerator.testMethodModel.GenericTestPoint;
import testGenerator.testMethodModel.ParamBinding;
import testGenerator.testMethodModel.TestCondition;
import testGenerator.testMethodModel.TestPath;
import testGenerator.testMethodModel.UserParamTestValue;

/**
 *
 * @author William
 */
public class BranchBuilderImp implements TestBuilderInterface
{

    private ArrayList<ParamBinding> params;
    private int currLevel;
    private TestPath currBranch;
    private ArrayList<TestPath> currBranchList;
    private Stack<TestCondition> doWhileCondtions;
    private AutoJUnitMethod currMethod;
    private Controller control;
    private GUIController guiControl;

    public BranchBuilderImp(GUIController guiControl, Controller control)
    {
        this.control = control;
        this.guiControl = guiControl;
        this.reset();
    }

    @Override
    public void addParam(String type, String id)
    {
        this.params.add(new ParamBinding(type, id));
    }

    @Override
    public void addDoWhileSlot(int level)
    {
        TestCondition cond = new TestCondition("??", EnumCondType.DOWHILE);
        doWhileCondtions.push(cond);
        this.addTestConditionAtLevel(cond, level);

    }

    @Override
    public void addDoWhileCondition(String doWhileCond, int level)
    {
        TestCondition cond = doWhileCondtions.pop();

        cond.setCondtion("while: " + doWhileCond);
        addDoWhileConditionAtLevel("Not " + doWhileCond, level);
    }

    @Override
    public void addSwitchCond(String cond, int level)
    {
        addTestConditionAtLevel("case: " + cond, level, EnumCondType.SWITCH);
    }

    @Override
    public void addIfConditionAtLevel(String ifCond, int level)
    {
        addTestConditionAtLevel("if: " + ifCond, level, EnumCondType.IF);
    }

    @Override
    public void addElseConditionAtLevel(String elseCond, int level)
    {
        addTestConditionAtLevel("else: " + elseCond, level, EnumCondType.ELSE);
    }

    @Override
    public void addWhileConditionAtLevel(String loopCond, int level)
    {
        addTestConditionAtLevel("while: " + loopCond, level, EnumCondType.WHILE);
    }

    @Override
    public void addForConditionAtLevel(String loopCond, int level)
    {
        addTestConditionAtLevel("for: " + loopCond, level, EnumCondType.FOR);
    }

    @Override
    public void saveMethod(String returnType, String name)
    {
        //Add the params to the new method
        for (ParamBinding param : this.params)
        {
            currMethod.params.add(0, param);
        }

        //Set the name and return type for the new method
        currMethod.name = name;
        currMethod.returnType = returnType;

        //Check to see if the last branch we were building has any conditions
        if (this.currBranch.conditions.size() > 0)
        {
            //Add last branch built
            this.currBranchList.add(currBranch);
        }

        //Set the method test branch to the current branch
        currMethod.testBranches = currBranchList;

        //Add a default branch in the case that no paths are present
        if (currMethod.testBranches.size() == 0)
        {
            //Add the default branch to the method
            addDefaultCondition();
        }

        //Create test param holders for each branch
        createTestPathParamHolders(currMethod);

        //Add a generic test point for each branch
        for (TestPath branch : currMethod.testBranches)
        {
            branch.genericTestPoints.add(new GenericTestPoint());
        }

        //Remove abstract methods
        if (isAcceptableMethod(currMethod))
        {
            this.control.addMethod(currMethod);
        }

        //Reset the branch builder for the next method
        this.reset();
    }

    @Override
    public void addModifier(String string)
    {
        this.currMethod.addModifier(string);
    }

    /**
     * Called by JavaBranchParser. Builds the branch based on the input given.
     * @param condition
     * @param level
     */
    private void addTestConditionAtLevel(String condition, int level, EnumCondType type)
    {
        addTestConditionAtLevel(new TestCondition(condition, type), level);
    }

    public void reset()
    {
        //Reset the current method
        currMethod = new AutoJUnitMethod();
        //Reset the current level
        currLevel = -1;
        //Create a new branch
        this.currBranchList = new ArrayList<TestPath>();
        //Create a new params list
        this.params = new ArrayList<ParamBinding>();
        //new branch
        this.currBranch = new TestPath();
        //New stack
        doWhileCondtions = new Stack<TestCondition>();
    }

    private void addDefaultCondition()
    {
        TestCondition defaultCond = new TestCondition("Default", EnumCondType.DEFAULT);
        TestPath defaultBranch = new TestPath();
        defaultBranch.conditions.add(defaultCond);
        this.currBranchList.add(0, defaultBranch);
    }

    private void createTestPathParamHolders(AutoJUnitMethod newMethod)
    {
        for (TestPath branch : newMethod.testBranches)
        {
            for (ParamBinding param : newMethod.params)
            {
                branch.userParamTestValues.add(new UserParamTestValue(param));
            }
        }
    }

    private void addDoWhileConditionAtLevel(String cond, int level)
    {
        addTestConditionAtLevel("while: " + cond, level, EnumCondType.DOWHILE);
    }

    private void addTestConditionAtLevel(TestCondition testCondition, int level)
    {
        //Case where we continue on current branch
        if (level > this.currLevel)
        {
            this.currBranch.conditions.add(testCondition);

            this.currLevel = level;
        }
        //Case where we continue on with part of old branch
        else if (level == this.currLevel)
        {
            TestPath oldBranch = new TestPath();
            for (TestCondition cond : this.currBranch.conditions)
            {
                oldBranch.conditions.add(cond);
            }
            this.currBranchList.add(oldBranch);

            //Remove last condtion so we have room for alternate branch
            this.currBranch.conditions.remove(this.currBranch.conditions.size() - 1);

            //Add new condtion
            this.currBranch.conditions.add(testCondition);

        }
        else
        {
            //Keep reference of last branch
            TestPath lastBranch = currBranch;

            //Save the current branch
            this.currBranchList.add(currBranch);

            //Create a new branch
            this.currBranch = new TestPath();

            //Add back in the last branch conds
            for (int i = 0; i < level; i++)
            {
                this.currBranch.conditions.add(lastBranch.conditions.get(i));
            }

            //Add new condtion
            this.currBranch.conditions.add(testCondition);

            this.currLevel = level;
        }
    }

    private boolean isAcceptableMethod(AutoJUnitMethod method)
    {
        if (method.hasModifier("abstract"))
        {
            return false;
        }
        else if (method.hasModifier("private"))
        {
            return false;
        }
        return true;
    }
}
